﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Drawing;
using System.Runtime.InteropServices;
using InteractiveScreen.Utilities;
using Cloo;

namespace InteractiveScreen.Segmentation
{
    public static class Segmenter
    {
        private static string _kernelSource = @"
#pragma OPENCL EXTENSION cl_khr_byte_addressable_store : enable

__kernel void SegmentSkin(__global uchar * originalImage,
					            int originalImageHeight,
								int originalImageChannels,
					            __global uchar * binarySkinBuffer)
{
	uint i = get_global_id(0);
	uint j = get_global_id(1);
	float red;
	float green;
	float blue;
	float sumRGB;
	float nRed;
	float nGreen;
	float nBlue;
	float nSumRGB;

    red = originalImage[i * originalImageHeight * originalImageChannels + j * originalImageChannels + 0];
    green = originalImage[i * originalImageHeight * originalImageChannels + j * originalImageChannels + 1];
    blue = originalImage[i * originalImageHeight * originalImageChannels + j * originalImageChannels + 2];

    sumRGB = red + green + blue;

    if (sumRGB == 0)
    {
	    binarySkinBuffer[i * originalImageHeight + j] = 0;
	    return;
    }

    nRed = red / sumRGB;
    nGreen = green / sumRGB;
    nBlue = blue / sumRGB;

    nSumRGB = nRed + nGreen + nBlue;

    if (nGreen != 0 && nSumRGB != 0)
    {
	    if (nRed / nGreen <= 1.185 || (nRed * nGreen) / (nSumRGB * nSumRGB) <= 0.112)
	    {
		    binarySkinBuffer[i * originalImageHeight + j] = 0;
		    return;
	    }
    }	

    binarySkinBuffer[i * originalImageHeight + j] = 255;
}

";
        private static ComputeContextPropertyList _properties;
        private static ComputeContext _context;
        private static ComputeProgram _program;
        private static ComputeKernel _kernel;
        private static ComputeBuffer<byte> _outputBuffer;
        private static ComputeEventList _events;
        private static ComputeCommandQueue _commands;
        private static GCHandle _binarySkinBufferHandle;
        private static int _wantedPlatformIndex = 0;
        private static int _warpedWholeFrameBufferWidth;
        private static int _warpedWholeFrameBufferHeight;
        private static byte[,] _warpedWholeFrameBinarySkinBuffer;
        private static int _predictionBoundingBoxBufferWidth;
        private static int _predictionBoundingBoxBufferHeight;
        private static byte[,] _predictionBoundingBoxBinarySkinBuffer;

        public static void Initialize(int warpedWholeFrameBufferWidth, int warpedWholeFrameBufferHeight, int predictionBoundingBoxBufferWidth, int predictionBoundingBoxBufferHeight)
        {
            _warpedWholeFrameBufferWidth = warpedWholeFrameBufferWidth;
            _warpedWholeFrameBufferHeight = warpedWholeFrameBufferHeight;

            _warpedWholeFrameBinarySkinBuffer = new byte[_warpedWholeFrameBufferWidth, _warpedWholeFrameBufferHeight];

            _predictionBoundingBoxBufferWidth = predictionBoundingBoxBufferWidth;
            _predictionBoundingBoxBufferHeight = predictionBoundingBoxBufferHeight;

            _predictionBoundingBoxBinarySkinBuffer = new byte[_predictionBoundingBoxBufferWidth, _predictionBoundingBoxBufferHeight];


            for (int i = 0; i < ComputePlatform.Platforms.Count; i++)
            {
                if (ComputePlatform.Platforms[i].Name == "NVIDIA CUDA")
                {
                    _wantedPlatformIndex = i;
                    break;
                }
            }

            _properties = new ComputeContextPropertyList(ComputePlatform.Platforms[_wantedPlatformIndex]);
            _context = new ComputeContext(ComputePlatform.Platforms[_wantedPlatformIndex].Devices, _properties, null, IntPtr.Zero);
            _program = new ComputeProgram(_context, new string[] { _kernelSource });
            _program.Build(ComputePlatform.Platforms[_wantedPlatformIndex].Devices, null, null, IntPtr.Zero);

            _kernel = _program.CreateKernel("SegmentSkin");

            _outputBuffer = new ComputeBuffer<byte>(_context, ComputeMemoryFlags.WriteOnly, _warpedWholeFrameBufferWidth * _warpedWholeFrameBufferHeight);
            _commands = new ComputeCommandQueue(_context, _context.Devices[0], ComputeCommandQueueFlags.None);

            _events = new ComputeEventList();
            _kernel.SetValueArgument<int>(1, _warpedWholeFrameBufferHeight);
            _kernel.SetValueArgument<int>(2, 3);
            _kernel.SetMemoryArgument(3, _outputBuffer);
        }
        public static byte[,] SegmentSkin(byte[, ,] frameBuffer)
        {
            byte[,] binarySkinBuffer = null;

            //binarySkinBuffer = SegmentSkinUsingCPU(frameBuffer);
            if (frameBuffer.GetLength(0) == _predictionBoundingBoxBufferWidth && frameBuffer.GetLength(1) == _predictionBoundingBoxBufferHeight)
                binarySkinBuffer = SegmentSkinUsingCPU(frameBuffer);
            else
                binarySkinBuffer = SegmentSkinUsingGPU(frameBuffer);

            return binarySkinBuffer;
        }
        public static byte[,] SegmentSkin(byte[, ,] frameBuffer, int frameBufferWidth, int frameBufferhHeight)
        {
            byte[,] binarySkinBuffer = new byte[frameBufferWidth, frameBufferhHeight];

            for (int i = 0; i < frameBufferWidth; i++)
            {
                for (int j = 0; j < frameBufferhHeight; j++)
                {
                    if (ConditionsRGB(frameBuffer[i, j, 0], frameBuffer[i, j, 1], frameBuffer[i, j, 2]))
                        binarySkinBuffer[i, j] = 255;
                    else
                        binarySkinBuffer[i, j] = 0;
                }
            }

            return binarySkinBuffer;
        }
        private static byte[,] SegmentSkinUsingGPU(byte[, ,] frameBuffer)
        {
            IntPtr frameBufferPtr;
            unsafe
            {
                fixed (byte* buffer = frameBuffer)
                {
                    frameBufferPtr = new IntPtr((void*)buffer);
                }
            }

            ComputeBuffer<byte> inputBuffer = new ComputeBuffer<byte>(_context, ComputeMemoryFlags.ReadOnly | ComputeMemoryFlags.CopyHostPointer, _warpedWholeFrameBufferWidth * _warpedWholeFrameBufferHeight * 3, frameBufferPtr);

            _kernel.SetMemoryArgument(0, inputBuffer);

            _commands.Execute(_kernel, null, new long[] { _warpedWholeFrameBufferWidth, _warpedWholeFrameBufferHeight }, null, null);

            _binarySkinBufferHandle = GCHandle.Alloc(_warpedWholeFrameBinarySkinBuffer, GCHandleType.Pinned);

            _commands.Read(_outputBuffer, false, 0, _warpedWholeFrameBufferWidth * _warpedWholeFrameBufferHeight, _binarySkinBufferHandle.AddrOfPinnedObject(), _events);
            _commands.Finish();

            _binarySkinBufferHandle.Free();

            return _warpedWholeFrameBinarySkinBuffer;
        }
        public static byte[,] SegmentSkinUsingCPU(byte[, ,] frameBuffer)
        {
            for (int i = 0; i < _predictionBoundingBoxBufferWidth; i++)
            {
                for (int j = 0; j < _predictionBoundingBoxBufferHeight; j++)
                {
                    if (ConditionsRGB(frameBuffer[i, j, 0], frameBuffer[i, j, 1], frameBuffer[i, j, 2]))
                        _predictionBoundingBoxBinarySkinBuffer[i, j] = 255;
                    else
                        _predictionBoundingBoxBinarySkinBuffer[i, j] = 0;
                }
            }

            return _predictionBoundingBoxBinarySkinBuffer;
        }
        private static bool ConditionsRGB(byte red, byte green, byte blue)
        {
            if (red > 95 && green > 40 && blue > 20
               || (Math.Max(red, Math.Max(green, blue)) - Math.Min(red, Math.Min(green, blue))) < 15
           && Math.Abs(red - green) > 15 && red > green && red > blue)
                return true;
            return false;
          #region Old Code
            //float sumRGB = (float)red + green + blue;

            //if (sumRGB == 0)
            //    return false;

            //float nRed = red / sumRGB;
            //float nGreen = green / sumRGB;
            //float nBlue = blue / sumRGB;

            //float nSumRGB = nRed + nGreen + nBlue;

            //if (nGreen != 0 && nSumRGB != 0)
            //{
            //    if (nRed / nGreen <= 1.185 || (nRed * nGreen) / (nSumRGB * nSumRGB) <= 0.112)
            //        return false;
            //}

            //return true;
#endregion
        }
    }
}
